#TODO
#
from sys import *
from config import *
from neuron import *

class NetworkLayers:
	#layers
	layers = []
	hidden_layers = []
	input_layer = []
	output_layer = []

	#constructors
	def __init__(self, cfg):
		self.cfg = cfg
		#creating layers & neurons
		self.input_layer = [Neuron(self.cfg, 0, i) for i in xrange(self.cfg.nna_npi)]
		self.hidden_layers = [[Neuron(self.cfg, layer + 1, neuron) for neuron in xrange(self.cfg.nna_nph)]\
						for layer in xrange(self.cfg.nna_hlayers)]
		self.output_layer = [Neuron(self.cfg, len(self.hidden_layers) or 1, j) for j in xrange(self.cfg.nna_npo)]
		self.layers = self.hidden_layers[:]
		self.layers.append(self.output_layer)
		self.layers.insert(0, self.input_layer)
		#weaving network
		self.weave()

	def weave(self):
		for number_of_layer, layer in enumerate(self.layers):
			for number_of_neuron, neuron in enumerate(layer):
				if layer != self.output_layer:
					neuron.connect_to_next_layer(self.layers[number_of_layer + 1])

	def apply(self, f, *args, **kwargs):
		for layer in self.layers:
			for neuron in layer:
				getattr(neuron, f)(*args, **kwargs)

	#main functions
	def propogate(self, train):
		#first = (None, 0.1 ** 100)
		#while first:
			#first = self.find_first_psp(first[1])
			#if first:

			for t in xrange(0,2500,10):
				#print t
				ct = t/1000.0
				for n in self.output_layer:
					n.change_membrane_potential(ct)
				res = self.emit_spike(ct,train)
				if res != None:
					return res
			return None


			#	first[0].change_membrane_potential(first[1])
			#	if first[0].emmit_spike(first[1], False):
			#		if train:
			#			first[0].train(first[1])
			#		return first[0].number_of_neuron
	def emit_spike(self,t,train):
		#print t
		for n in self.output_layer:
					if n.emmit_spike(t, False):
						if train:
							n.train(t)
						return n.number_of_neuron

		if self.cfg.synergy:
			self.synergy()
			for n in self.output_layer:
						if n.emmit_spike(t, False):
							#print 'synergy!'
							if train:
								n.train(t)
							return n.number_of_neuron
		return None

	def synergy(self):
		synergy = {}
		for n in self.output_layer:
			synergy[n] =  self.cfg.synergy * n.x

		for n in self.output_layer:
			for k in synergy.keys():
					if n != k:
						n.x += synergy[k]


	def inhibit_output(self,neuron):
		for n in self.output_layer:
			if n != neuron:
				n.inhibit_terminals()

	# secondary functions
	def present(self, pattern):
		for input_number, input in enumerate(pattern):
			self.input_layer[input_number].emmit_spike(input, True)

	def find_first_psp(self, t):
		first_neuron = None
		first_psp = 10 ** 10
		for layer in self.layers:
			for neuron in layer:
				if neuron.pending_changes:
					future_changes = [x for x in neuron.pending_changes if x > t]
					if future_changes:
						future_min_change = min(future_changes)
						if future_min_change < first_psp:
								first_neuron = neuron
								first_psp = future_min_change
		if first_neuron:

			#debug
			if self.cfg.debug: self.cfg.log('future_min_change = %1.3f | %d'%(future_min_change, first_neuron.number_of_neuron))

			return (first_neuron, future_min_change)
		return None

#######################################################################

	def get_f2f(self, t):
		for neuron in self.output_layer:
			neuron.spikes = neuron.spikes or []
			if t in neuron.spikes:
				return neuron.number_of_neuron
		return -1

	def get_number_of_synapses(self):
		sum = 0
		for number_of_layer, layer in enumerate(self.layers):
			for number_of_neuron, neuron in enumerate(layer):
				if number_of_layer and neuron.ps:
					sum += len(neuron.ps)
		return sum

	#text output functions
	def __str__(self):
		out = ''
		maximum_number_of_neurons = max(self.cfg.nna_npi, \
					self.cfg.nna_npo, self.cfg.nna_nph)
		for number_of_neuron in xrange(maximum_number_of_neurons):
			out += '\t'
			for number_of_layer, layer in enumerate(self.layers):
				try:
					out += str(self.layers[number_of_layer][number_of_neuron]) + '\t'
				except:
					out += '\t\t\t\t'
			out += '\n'
		return out

	def terminals_to_str(self):
		result = 'terminals (w,d)\n\t'
		out = []
		for number_of_neuron, neuron in enumerate(self.output_layer):
			out_inner = []
			for number_of_synapse, synapse in enumerate(neuron.ps):
				s = '[' + str(synapse.emitter.number_of_layer) + \
								  ',' + str(synapse.emitter.number_of_neuron) + ']'
				for number_of_terminal, terminal in enumerate(synapse.terminals):
					s += '(%1.3f,%1.3f) '%(terminal.weight, terminal.delay)
				out_inner.append(s)
			out.append(out_inner)

		for i in xrange(len(out[0])):
			for j in xrange(len(out)):
				result += out[j][i] + '\t'
			result += '\n\t'

		return result